#include "scrollbar.h"
#include <allegro5/allegro5.h>
#include <allegro5/allegro_primitives.h>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <cmath>
#include "scrollskin.h"
using namespace std;
using namespace boost;

ScrollBar::ScrollBar(ScrollSkin *skin, ScrollBar::Direction dir)
: m_direction(dir), m_min(0), m_max(100), m_pos(0), m_step(1), 
  m_pSkin(skin), m_length(0), m_dragging(false), m_dragLength(0), m_pxPos(0) {
     setPxPos();
}

ScrollBar::~ScrollBar() {
}

ScrollBar::Direction ScrollBar::direction() const {
     return m_direction;
}

void ScrollBar::setDirection(ScrollBar::Direction dir) {
     m_direction = dir;
}

int ScrollBar::minimum() const {
     return m_min;
}

void ScrollBar::setMinimum(int min) {
     if (min < 0)
	  min = 0;
     if (min > m_max)
	  min = m_max;
     m_min = min;
     setPxPos();
}

int ScrollBar::maximum() const {
     return m_max;
}

void ScrollBar::setMaximum(int max) {
     if (max < m_min)
	  max = m_min;
     m_max = max;
     setPxPos();
}

int ScrollBar::pos() const {
     return m_pos;
}

void ScrollBar::setPos(int pos) {
     if (pos < m_min)
	  pos = m_min;
     if (pos > m_max)
	  pos = m_max;
     m_pos = pos;
     setPxPos(); // set pixel position of handle for new pos
     // call onPosChange connected slots
     m_onPosChange(m_pos);
}

void ScrollBar::setStart(Point start) {
     m_start = start;
     setPxPos();
}

void ScrollBar::setLength(int length) {
     m_length = length;
     setPxPos();
}

Rect ScrollBar::rect() const {
     int thick = m_pSkin->thickness();
     Rect r(m_start.x(),m_start.y());
     if (m_direction == ScrollBar::Horizontal) {
	  r.setWidth(m_length);
	  r.setHeight(thick);
     } else {
	  r.setWidth(thick);
	  r.setHeight(m_length);
     }
     return r;
}

// px length of scroll area
int ScrollBar::scrollLength() {
     int thick = m_pSkin->thickness();
     int slen = max(0, m_length - thick * 3);
     return slen;
}

Rect ScrollBar::handleRect() {
     int thick = m_pSkin->thickness();
     Rect ret;
     if (m_direction == ScrollBar::Horizontal)
	  ret = Rect(m_pxPos, m_start.y(), thick, thick);
     else
	  ret = Rect(m_start.x(), m_pxPos, thick, thick);
     return ret;
}

Rect ScrollBar::upRect() {
     int thick = m_pSkin->thickness();
     Rect r(0,0,thick,thick);
     if (m_direction == ScrollBar::Horizontal) {
	  r.setX(m_length + m_start.x() - thick);
	  r.setY(m_start.y());
     } else {
	  r.setX(m_start.x());
	  r.setY(m_length + m_start.y() - thick);
     }
     return r;
}

Rect ScrollBar::downRect() {
     int thick = m_pSkin->thickness();
     Rect r(m_start.x(),m_start.y(),thick,thick);
     return r;
}

// set the handle pixel position from the current value (m_pos)
void ScrollBar::setPxPos() {
     double d = m_max - m_min;
     if (!d)
	  d = 1;
     double p = (double)(m_pos - m_min) / d;
     if (p < 0)
	  p = 0;
     if (p > 1)
	  p = 1;
     int thick = m_pSkin->thickness();
     int slen = scrollLength();
     if (m_direction == ScrollBar::Horizontal)
	  m_pxPos = round(p * (double)slen) + m_start.x() + thick;
     else 
	  m_pxPos = round(p * (double)slen) + m_start.y() + thick;
     m_needPaint = true;
}

void ScrollBar::setPxPos(int px) {
     int thick = m_pSkin->thickness();
     int min=0;
     if (m_direction == ScrollBar::Horizontal)
	  min = m_start.x() + thick;
     else
	  min = m_start.y() + thick;
     int max = min + m_length - thick * 3;
     if (px < min)
	  px = min;
     if (px > max)
	  px = max;
     m_pxPos = px;
     m_needPaint = true;
}

void ScrollBar::paint(bool all) {

     int thick = m_pSkin->thickness();
     // clear
     Rect r = rect();
     al_draw_filled_rectangle(r.x(), r.y(), r.x() + r.width(), 
			      r.y() + r.height(), al_map_rgb(0,0,0));
     int pxlen;
     Rect posRect = handleRect();
     if (m_direction == ScrollBar::Horizontal) {
	  // draw bar
	  int endx = m_start.x() + m_length - thick;
	  for (int x = m_start.x() + thick; x < endx; x += thick) {
	       pxlen = min(thick, endx - x);
	       al_draw_bitmap_region(m_pSkin->bitmap(), thick * 2, 0,
				     pxlen, thick, x, m_start.y(), 0);
	  }
	  // draw ends
	  al_draw_bitmap_region(m_pSkin->bitmap(), 0, 0, thick, thick, 
				m_start.x(), m_start.y(), ALLEGRO_FLIP_HORIZONTAL);
	  al_draw_bitmap_region(m_pSkin->bitmap(), 0, 0, thick, thick, 
				endx, m_start.y(), 0);
	  // draw position
	  al_draw_bitmap_region(m_pSkin->bitmap(), thick, 0, thick, thick,
				posRect.x(), posRect.y(), 0);
     } else if (m_direction == ScrollBar::Vertical) {
	  int endy = m_start.y() + m_length - thick;
	  // draw the bar
	  for (int y = m_start.y() + thick; y < endy; y += thick) {
	       pxlen = min(thick, endy - y);
	       al_draw_bitmap_region(m_pSkin->rotBitmap(), 0, thick * 2, 
				     thick, pxlen, m_start.x(), y, 0);
	  }
	  // draw ends
	  al_draw_bitmap_region(m_pSkin->rotBitmap(), 0, 0, thick, thick, 
				m_start.x(), m_start.y(), ALLEGRO_FLIP_VERTICAL);
	  al_draw_bitmap_region(m_pSkin->rotBitmap(), 0, 0, thick, thick, 
				m_start.x(), endy, 0);
	  // draw position
	  al_draw_bitmap_region(m_pSkin->rotBitmap(), 0, thick, thick, thick,
				posRect.x(), posRect.y(), 0);
     }
     m_needPaint = false;
}

void ScrollBar::mouseDown(Point p, int button) {
     // start drag if mouse is on handle
     if (button == 1 && handleRect().inside(p) && !m_dragging) {
	  m_dragLength = 0;
	  m_dragging = true;
     }
}

void ScrollBar::mouseUp(Point p, int button) {
     if (button == 1) {
	  if (m_dragging) {
	       m_dragging = false;
	       dragDone();
	  }
	  if (upRect().inside(p)) {
	       setPos(min(m_max, m_pos + 1));
	  } else if (downRect().inside(p)) {
	       setPos(max(m_min,m_pos - 1));
	  }
     }
}

void ScrollBar::dragDone() {
     m_dragging = false;
     setPosAtPx(m_pxPos);
     setPxPos();
}

string ScrollBar::toString() {
     ostringstream os;
     os << "ScrollBar{ " << "m_min=" << m_min << ", m_max=" << m_max \
       << ", m_pos=" << m_pos << "}" << endl;
     return os.str();
}

void ScrollBar::mouseMove(Point p, Point dp) {
     // find out if button is still down - might have been released elsewhere
     ALLEGRO_MOUSE_STATE mouseState;
     al_get_mouse_state(&mouseState);
     bool buttonDown = al_mouse_button_down(&mouseState,1);
     if (m_dragging) {
	  if (!buttonDown) {
	       dragDone();
	       return;
	  }
	  // set pos based on total distance dragged
	  if (m_direction == ScrollBar::Horizontal)
	       setPxPos(p.x() - m_pSkin->thickness() / 2);
	  else
	       setPxPos(p.y() - m_pSkin->thickness() / 2);
	  setPosAtPx(m_pxPos);
     } else if (m_grabMouse) { // stop dragging
	  m_grabMouse = false;
     }
}

// set the scroll bar position to that closest to pixel position p
void ScrollBar::setPosAtPx(int p) {
     double relPos = p - m_pSkin->thickness();
     if (m_direction == ScrollBar::Horizontal)
	  relPos -= m_start.x();
     else
	  relPos -= m_start.y();
     double len = scrollLength();
     double d = relPos / len;
     int pos = d * (m_max - m_min) + m_min;
     m_pos = pos;
     m_onPosChange(m_pos);
}

void ScrollBar::mouseExit(Point p) {
     // keep receiving move events if button 1 is down - dragging
     if (m_dragging)
	  m_grabMouse = true;
}

signals2::connection ScrollBar::onPosChange(const OnPosChange::slot_type &callback) {
     return m_onPosChange.connect(callback);
}

const ScrollSkin* ScrollBar::scrollSkin() {
     return m_pSkin;
}
